package munci.read;

import javacc.InputConstants;

public abstract class Token implements CharSequence {
	private final String image;
	
	private String source;
	private int line;
	
	Token(String image) {
		this.image = image;
	}
	
	public String id() {
		return this.image;
	}
	
	public String image() {
		return this.image;
	}
	
	public String source() {
		return this.source;
	}
	
	public int line() {
		return this.line;
	}
	
	public static Token createToken(javacc.Token tok, String source) {
		Token token = newToken(tok);
		token.line = tok.beginLine;
		token.source = source;
		return token;
	}
	
	private static Token newToken(javacc.Token tok) {
		switch (tok.kind) {
    	case InputConstants.IDENTIFIER:
    		return new Identifier(tok.image);
    	case InputConstants.SYMBOL:
    		return new Symbol(tok.image);
		case InputConstants.INT_LITERAL:
			return new Literal.Int(tok.image);
		case InputConstants.LONG_LITERAL:
			return new Literal.Long(tok.image);
		case InputConstants.FLOATING_POINT_LITERAL:
			return (tok.image.endsWith("f") || tok.image.endsWith("F")) ? 
					new Literal.Float(tok.image) : new Literal.Double(tok.image);
		case InputConstants.CHARACTER_LITERAL:
			return new Literal.Char(tok.image);
		case InputConstants.STRING_LITERAL:
			return new Literal.Str(tok.image);
    	default:
    		throw new RuntimeException("Unknown token type: " + InputConstants.tokenImage[tok.kind]);
		}
	}
	
	public int length() {
		return image.length();
	}

	public char charAt(int index) {
		return image.charAt(index);
	}

	public CharSequence subSequence(int start, int end) {
		return image.subSequence(start, end);
	}
	
	@Override public String toString() {
		return image; // must return image to satisfy CharSequence contract
	}
	
	@Override public boolean equals(Object obj) {
		if (!obj.getClass().equals(getClass()))
			return false;
		
		return ((Token) obj).image == image;
	}
	
	@Override public int hashCode() {
		return image.hashCode();
	}
	
	public static class Identifier extends Token {
		public Identifier(String image) {super(image);}
	}
	public static class Symbol extends Token {
		public Symbol(String image) {super(image);}
	}
	
	public abstract static class Literal extends Token {
		public static final String ID = "<literal>";
		
		Literal(String image) {super(image);}
		
		@Override public String id() {
			return ID;
		}

		public static class Char extends Literal {
			public Char(String image) {super(image);}
		}

		public static class Int extends Literal {
			public Int(String image) {super(image);}
		}

		public static class Long extends Literal {
			public Long(String image) {super(image);}
		}

		public static class Float extends Literal {
			Float(String image) {super(image);}
		}

		public static class Double extends Literal {
			public Double(String image) {super(image);}
		}

		public static class Str extends Literal {
			public Str(String image) {super(image);}
		}
	}
	
	public static Token get(String image) {
		char char0 = image.charAt(0);
		if (Character.isJavaIdentifierStart(char0)) {
			return new Token.Identifier(image);
		} else if (char0 == '"') {
			return new Token.Literal.Str(image);
		} else if (char0 == '\'') {
			return new Token.Literal.Char(image);
		} else {	
			return new Token.Symbol(image);
		}
	}
}
